/*
 * Copyright 2019 Web3 Labs Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.web3j.crypto;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.List;

import org.bouncycastle.util.encoders.Hex;
import org.web3j.crypto.transaction.type.TransactionType;
import org.web3j.rlp.RlpEncoder;
import org.web3j.rlp.RlpList;
import org.web3j.rlp.RlpType;
import org.web3j.utils.Numeric;

/**
 * Create RLP encoded transaction, implementation as per p4 of the
 * <a href="http://gavwood.com/paper.pdf">yellow paper</a>.
 */
public class TransactionEncoder {

	private static final int CHAIN_ID_INC = 35;
	private static final int LOWER_REAL_V = 27;

	public static byte[] signMessage(RawTransaction rawTransaction, Credentials credentials) {
		byte[] encodedTransaction = encode(rawTransaction);
		System.out.println("000000000000000000000000000000");
		System.out.println(Hex.toHexString(encodedTransaction));
		System.out.println("000000000000000000000000000000");
		Sign.SignatureData signatureData = Sign.signMessage(encodedTransaction, credentials.getEcKeyPair());

		return encode(rawTransaction, signatureData);
	}

	public static byte[] signMessage(RawTransaction rawTransaction, long chainId, Credentials credentials) {

		if (!rawTransaction.getType().equals(TransactionType.LEGACY)) {
			return signMessage(rawTransaction, credentials);
		}

		byte[] encodedTransaction = encode(rawTransaction, chainId);
		System.out.println("000000000000000000000000000000");
		System.out.println(Hex.toHexString(encodedTransaction));
		System.out.println("000000000000000000000000000000");
		Sign.SignatureData signatureData = Sign.signMessage(encodedTransaction, credentials.getEcKeyPair());
		System.out.println("-------------------------");
		System.out.println(Hex.toHexString(signatureData.getR()));
		System.out.println(Hex.toHexString(signatureData.getS()));
		System.out.println(Hex.toHexString(signatureData.getV()));
		System.out.println("-------------------------");

		Sign.SignatureData eip155SignatureData = createEip155SignatureData(signatureData, chainId);
		
		return encode(rawTransaction, eip155SignatureData);
	}

	@Deprecated
	public static byte[] signMessage(RawTransaction rawTransaction, byte chainId, Credentials credentials) {
		return signMessage(rawTransaction, (long) chainId, credentials);
	}

	public static Sign.SignatureData createEip155SignatureData(Sign.SignatureData signatureData, long chainId) {
//        BigInteger v = Numeric.toBigInt(signatureData.getV());
//        // (v - 27 + chainId * 2)  + 35
//        v = v.subtract(BigInteger.valueOf(LOWER_REAL_V));
//        v = v.add(BigInteger.valueOf(chainId).multiply(BigInteger.valueOf(2)));
//        v = v.add(BigInteger.valueOf(CHAIN_ID_INC));
//
//        return new Sign.SignatureData(v.toByteArray(), signatureData.getR(), signatureData.getS());

		return signatureData;
	}

	@Deprecated
	public static Sign.SignatureData createEip155SignatureData(Sign.SignatureData signatureData, byte chainId) {
		return createEip155SignatureData(signatureData, (long) chainId);
	}

	public static byte[] encode(RawTransaction rawTransaction) {
//		return encode(rawTransaction, null);
		Sign.SignatureData signatureData = new Sign.SignatureData(new byte[] {}, new byte[] {}, new byte[] {});
		return encode(rawTransaction, signatureData);
	}

	public static byte[] encode(RawTransaction rawTransaction, long chainId) {

//		Sign.SignatureData signatureData =
//                new Sign.SignatureData(longToBytes(chainId), new byte[] {}, new byte[] {});
		Sign.SignatureData signatureData = new Sign.SignatureData(new byte[] {}, new byte[] {}, new byte[] {});

		return encode(rawTransaction, signatureData);
	}

	@Deprecated
	public static byte[] encode(RawTransaction rawTransaction, byte chainId) {
		return encode(rawTransaction, (long) chainId);
	}

	private static byte[] encode(RawTransaction rawTransaction, Sign.SignatureData signatureData) {
		List<RlpType> values = asRlpValues(rawTransaction, signatureData);
		RlpList rlpList = new RlpList(values);
		byte[] encoded = RlpEncoder.encode(rlpList);
		
		if (!rawTransaction.getType().equals(TransactionType.LEGACY)) {
			return ByteBuffer.allocate(encoded.length + 1)//
					.put(rawTransaction.getType().getRlpType())//
					.put(encoded)//
					.array();
		}
		return encoded;
	}

	private static byte[] longToBytes(long x) {
		ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
		buffer.putLong(x);
		return buffer.array();
	}

	public static List<RlpType> asRlpValues(RawTransaction rawTransaction, Sign.SignatureData signatureData) {
		return rawTransaction.getTransaction().asRlpValues(signatureData);
	}
}
